package cn.sciento.boot.data.change.intercept;

import io.choerodon.mybatis.domain.EntityTable;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.InterceptorOrder;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import cn.sciento.boot.data.change.dto.ChangeData;
import cn.sciento.boot.data.change.dto.MybatisInvocation;
import cn.sciento.boot.data.change.dto.SqlParserInfo;
import cn.sciento.boot.data.change.handler.DataChangeBaseHandler;
import cn.sciento.boot.data.change.parse.DataParser;
import cn.sciento.boot.data.change.parse.ParserFactory;
import cn.sciento.boot.data.change.selector.SqlInfo;
import cn.sciento.boot.data.change.selector.manager.EntityTableSelectorManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
@InterceptorOrder(499)
public class DataChangeInterceptor implements Interceptor {
  private static final Logger LOGGER = LoggerFactory.getLogger(DataChangeInterceptor.class);
  
  private List<DataChangeBaseHandler> handlerList;
  
  private EntityTableSelectorManager entityTableSelectorManager;
  
  public DataChangeInterceptor(List<DataChangeBaseHandler> handlerList, EntityTableSelectorManager entityTableSelectorManager) {
    this.handlerList = handlerList;
    this.entityTableSelectorManager = entityTableSelectorManager;
  }
  
  public Object intercept(Invocation invocation) throws Throwable {
    Object target = invocation.getTarget();
    Executor executor = (Executor)target;
    Object[] args = invocation.getArgs();
    MappedStatement ms = (MappedStatement)args[0];
    Object parameter = args[1];
    SqlParserInfo sqlParserInfo = SqlParserInfo.getInstance(ms.getBoundSql(parameter).getSql(), ms.getSqlCommandType());
    EntityTable table = this.entityTableSelectorManager.getEntityTable(new SqlInfo(sqlParserInfo.getTableName(), ms.getId()));
    if (null == table || !ifNeedIntercept(table) || null == table.getKeyProperties()) {
      LOGGER.debug("do not need to intercept for table name:{}", sqlParserInfo.getTableName());
      return invocation.proceed();
    } 
    LOGGER.debug("start to intercept for table name:{}", sqlParserInfo.getTableName());
    DataParser dataParser = ParserFactory.getInstance().creator(ms.getSqlCommandType());
    MybatisInvocation mybatisInvocation = MybatisInvocation.getInstance(invocation, ms, parameter, executor, table);
    List<Map<String, Object>> parseBefore = dataParser.parseBefore(mybatisInvocation);
    Object proceed = invocation.proceed();
    List<Map<String, Object>> parseAfter = dataParser.parseAfter(mybatisInvocation, parseBefore);
    List<ChangeData> changeDatas = dataParser.buildChangeData(parseBefore, parseAfter, mybatisInvocation);
    doHandle(table, changeDatas);
    return proceed;
  }
  
  public Object plugin(Object target) {
    if (target instanceof Executor)
      return Plugin.wrap(target, this); 
    return target;
  }
  
  public void setProperties(Properties properties) {}
  
  private Boolean ifNeedIntercept(EntityTable table) {
    if (CollectionUtils.isEmpty(this.handlerList))
      return Boolean.FALSE;
    for (DataChangeBaseHandler dataChangeBaseHandler : this.handlerList) {
      if (dataChangeBaseHandler.canProcess(table))
        return Boolean.TRUE;
    } 
    return Boolean.FALSE;
  }
  
  private void doHandle(EntityTable table, List<ChangeData> changeDataList) {
    if (CollectionUtils.isNotEmpty(changeDataList)) {
      LOGGER.debug("start to handle for :{}", table.getName());
      this.handlerList.forEach(item -> item.handle(table, changeDataList));
    } 
  }
}
